﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace Lion.WeiXin.Tools.Log
{
   public class LogHelper
    {
        private static readonly string LogPathTemplate =  @"{0}Logs\{3}\{2}\{1:yyyy\\M}\";
        private static readonly string LogFilenameTemplate = "{0:yyyy-MM-dd-HH}.log";

        /// <summary>
        /// 同步锁定对象
        /// </summary>
        private static object sync = new object();

        private static string _appName;
        private static string _basePath;
        private static string _filePath;
        private static string _defaultLogDirectoryName = "System";
        //private static MethodInfo _logMethod;
        //private static object _contractInstance;
        //private static bool _isLoadRemotingComponentFailed = false;       

        private static DateTime _lastSweepTime = DateTime.Now;


        #region 对外接口
       
        static string _Info = "info";
        static string _Error = "error";

        public static void Custom(string message, string dir)
        {
            WriteLog(string.Format("#C|{2}|[{0}]{1}", DateTime.Now.ToString("HH:mm:ss:fff"), message, Thread.CurrentThread.ManagedThreadId), dir);
        }

        public static void Custom(string formatMessage, string dir, params object[] parameters)
        {
            string content = string.Format("#C|{1}|[{0}]", DateTime.Now.ToString("HH:mm:ss:fff"), Thread.CurrentThread.ManagedThreadId) + string.Format(formatMessage, parameters);
            WriteLog(content, dir); 
        }

        public static void Info(string message)
        {
            WriteLog(string.Format("#I|{2}|[{0}]{1}", DateTime.Now.ToString("HH:mm:ss:fff"), message, Thread.CurrentThread.ManagedThreadId), _Info);
        }

        public static void Info(string formatMessage, params object[] parameters)
        {
            string content = string.Format("#I|{1}|[{0}]", DateTime.Now.ToString("HH:mm:ss:fff"), Thread.CurrentThread.ManagedThreadId) + string.Format(formatMessage, parameters);
            WriteLog(content, _Info);
        }

        public static void Error(Exception ex, string message)
        {
            WriteLog(string.Format("#E|{4}|[{0}]{1}。原因={2},堆栈={3}", DateTime.Now.ToString("HH:mm:ss:fff"), message, ex.Message, ex.StackTrace, Thread.CurrentThread.ManagedThreadId), _Error);
        }

        public static void Error(Exception ex, string formatMessage, params object[] parameters)
        {
            string content = string.Format("#E|{4}|[{0}]{1}。原因={2},堆栈={3}", DateTime.Now.ToString("HH:mm:ss:fff"), string.Format(formatMessage, parameters), ex.Message, ex.StackTrace, Thread.CurrentThread.ManagedThreadId);
            WriteLog(content, _Error);
        }

        #endregion


        private struct OpenedFile
        {
            public string Filename;
            public DateTime LastWriteTime;
            public TextWriter Writer;
        }

        private static Dictionary<string, OpenedFile> _files = new Dictionary<string, OpenedFile>();

        /// <summary>
        /// 初始化临时目录
        /// </summary>
        private static void InitTempPath()
        {
            if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("USSDTEMP")))
            {
                _filePath = Path.GetTempPath();
                _filePath = _filePath.EndsWith("\\") ? _filePath : _filePath + "\\";
                _filePath = _filePath + "USSD\\";
            }
            else
            {
                _filePath = Environment.GetEnvironmentVariable("USSDTEMP");
            }
            if (!Directory.Exists(_filePath))
            {
                Directory.CreateDirectory(_filePath);
            }
        }

        /// <summary>
        /// 检查是否已完成了临时目录的初始化
        /// </summary>
        private static void CheckTempPath()
        {
            if (string.IsNullOrEmpty(_filePath))
            {
                InitTempPath();
            }
        }

        /// <summary>
        /// 写入键值
        /// </summary>
        /// <param name="message"></param>
        private static void WriteEntry(string message)
        {
            CheckTempPath();
            WriteEntry(message, _filePath, "tmp");
        }

        /// <summary>
        /// 写入键值
        /// </summary>
        /// <param name="message">写入的信息</param>
        /// <param name="pathName">文件的存储位置</param>
        /// <param name="extName">写入文件的扩展名</param>
        private static void WriteEntry(string message, string pathName, string extName)
        {
            try
            {
                Monitor.Enter(sync);
                pathName = pathName.EndsWith("\\") ? pathName : pathName + "\\";
                int i = 0;
                string name = "";
                while (File.Exists(name = pathName + (++i).ToString("X") + "." + extName)) ;
                StreamWriter sw = new StreamWriter(name);
                sw.Write(message);
                sw.Flush();
                sw.Close();
                sw.Dispose();
            }
            catch
            {
            }
            finally
            {
                Monitor.Exit(sync);
            }
        }

        /// <summary>
        /// 写入日志的一个区域的开始部分
        /// </summary>
        /// <param name="unformatMessage"></param>
        /// <param name="parameters"></param>
        /// <returns>当前时间</returns>
        private static DateTime WriteStartTag(string unformatMessage, params string[] parameters)
        {
#if DEBUG
            return WriteStartTag(string.Format(unformatMessage, parameters));
#else
            return DateTime.Now;
#endif

        }

        /// <summary>
        /// 写入日志的一个区域的开始部分
        /// </summary>
        /// <param name="message"></param>
        private static DateTime WriteStartTag(string message)
        {
#if DEBUG
            string startTag = string.Concat("=========================== "
                                            , message
                                            , " ===========================\r\n\r\n");
            WriteLog(startTag);

            return DateTime.Now;
#else
            return DateTime.Now;
#endif
        }

        /// <summary>
        /// 写入日志的一个区域的结尾部分
        /// </summary>
        private static void WriteEndTag()
        {
            WriteEndTag(TimeSpan.FromTicks(DateTime.MinValue.Ticks));
        }

        /// <summary>
        /// 写入日志的一个区域的结尾部分
        /// </summary>
        /// <param name="ts"></param>
        private static void WriteEndTag(TimeSpan ts)
        {
#if DEBUG
            WriteEndTag(ts, "查询完成");
#endif

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ts"></param>
        /// <param name="message"></param>
        private static void WriteEndTag(TimeSpan ts, string message)
        {
#if DEBUG
            if (ts != TimeSpan.FromTicks(DateTime.Now.Ticks))
            {
                WriteLog(string.Format("=========================== " + message + "，运行时间：{0} ms ===========================\r\n\r\n", ts.TotalMilliseconds));

            }
            else
            {
                WriteLog("=======================================================================================\r\n\r\n");

            }
#endif

        }

        /// <summary>
        /// 写入日志文件，该操作是线程安全的
        /// </summary>
        /// <param name="log"></param>
        /// <param name="eLevel"></param>
        /// <param name="parameters"></param>
        private static void WriteLog(string log, ExceptionLevel eLevel, params object[] parameters)
        {
            WriteLog(string.Format(log, parameters), eLevel);
        }
        /// <summary>
        /// 写入日志文件，该操作是线程安全的
        /// </summary>
        /// <param name="log"></param>
        private static void WriteLog(string log)
        {
            WriteLog(log, ExceptionLevel.Infomation);
        }
        /// <summary>
        /// 写入日志文件，该操作是线程安全的
        /// </summary>
        /// <param name="log"></param>
        /// <param name="eLevel"></param>
        private static void WriteLog(string log, ExceptionLevel eLevel)
        {

            Write(log, _defaultLogDirectoryName, true, eLevel, LogPathTemplate, LogFilenameTemplate);
        }

        /// <summary>
        /// 写入日志文件，该操作是线程安全的
        /// </summary>
        /// <param name="log"></param>
        /// <param name="logDir"></param>
        private static void WriteLog(string log, string logDir)
        {
            WriteLog(log, logDir, ExceptionLevel.None);
        }

        /// <summary>
        /// 写入日志文件，该操作是线程安全的
        /// </summary>
        /// <param name="log"></param>
        /// <param name="logDir"></param>
        /// <param name="eLevel"></param>
        private static void WriteLog(string log, string logDir, ExceptionLevel eLevel)
        {
            Write(log, logDir, false, eLevel, LogPathTemplate, LogFilenameTemplate);
        }

        /// <summary>
        /// 写入日志文件，该操作是线程安全的
        /// </summary>
        /// <param name="log"></param>
        /// <param name="logDir"></param>
        /// <param name="logTime"></param>
        /// <param name="eLevel"></param>
        private static void WriteLog(string log, string logDir, bool logTime, ExceptionLevel eLevel)
        {
            Write(log, logDir, logTime, eLevel, LogPathTemplate, LogFilenameTemplate);
        }

        /// <summary>
        /// 写入日志文件，该操作是线程安全的
        /// </summary>
        /// <param name="log"></param>
        /// <param name="logDir"></param>
        /// <param name="logTime"></param>
        /// <param name="eLevel"></param>
        /// <param name="filenameTemplate">
        /// {0}: 当前时间；
        /// 默认值为"{0:yyyy-MM-dd-HH}.log"
        /// </param>
        /// <param name="pathTemplate">
        /// {0}: 程序的基础路径；
        /// {1}: 当前时间；
        /// {2}: 记录的日志文件夹；
        /// {3}: 应用程序的名字；
        /// 默认值为@"{0}Logs\{3}\{2}\{1:yyyy\\M}\"
        /// </param>
        private static void WriteLog(string log, string logDir, bool logTime, ExceptionLevel eLevel, string pathTemplate, string filenameTemplate)
        {
            Write(log, logDir, logTime, eLevel, pathTemplate, filenameTemplate);
        }

        /// <summary>
        /// 写入日志文件，该操作是线程安全的
        /// </summary>
        /// <param name="filenameTemplate"></param>
        /// <param name="pathTemplate"></param>
        /// <param name="log"></param>
        /// <param name="logDir"></param>
        /// <param name="logTime"></param>
        /// <param name="eLevel"></param>
        private static void Write(string log, string logDir, bool logTime, ExceptionLevel eLevel, string pathTemplate, string filenameTemplate)
        {
            try
            {
                Monitor.Enter(sync);

                if (string.IsNullOrEmpty(_appName))
                {
                    FileInfo fi = new FileInfo(Process.GetCurrentProcess().MainModule.ModuleName);
                    try
                    {
                        _appName = fi.Name.Remove(fi.Name.Length - fi.Extension.Length);
                    }
                    catch
                    {
                        _appName = "Null";
                    }
                    _basePath = AppDomain.CurrentDomain.BaseDirectory;
                }
                DateTime curDatetime = DateTime.Now;
                string path = string.Format(string.IsNullOrEmpty(pathTemplate) ? LogPathTemplate : pathTemplate, _basePath, curDatetime, logDir, _appName);

                //CreateDirectory(filename);

                string filename = String.Format(string.IsNullOrEmpty(filenameTemplate) ? LogFilenameTemplate : filenameTemplate, curDatetime);
                string strTime = logTime ? curDatetime.ToString("[HH:mm:ss.fff]:") : "";
                WriteIntoFile(String.Format("{3}{2}{0}{1}", strTime, log, GetToken(eLevel), eLevel == ExceptionLevel.None ? "" : "#"), path, filename);
  
              
            }
            catch (Exception ex)
            {
                string errMessage = "写入日志文件";
                try
                {
                    DateTime curDatetime = DateTime.Now;
                    string content = String.Format(
                        "{3}{2}{0}{1}",
                        logTime ? curDatetime.ToString("[HH:mm:ss.fff]:") : "",
                        log,
                        GetToken(eLevel),
                        eLevel == ExceptionLevel.None ? "" : "#");
                    string filename = String.Format(string.IsNullOrEmpty(filenameTemplate) ? LogFilenameTemplate : filenameTemplate, curDatetime);
                    errMessage = string.Format("写[{0}]至日志[{1}]文件", content, filename);
                }
                catch (Exception) { }
#if DEBUG
                Console.WriteLine(errMessage + "时出现异常，异常信息：" + ex.Message);
#endif
                LogExceptionToEventLog(ex, errMessage);
            }
            finally
            {
                Monitor.Exit(sync);
            }
        }

        private static void LogExceptionToEventLog(Exception ex, string errMessage)
        {
            string exMessage = String.Format(
                 "{3}时出现未处理的异常[{2}]\r\n异常信息：{0}\r\n堆栈信息：\r\n{1}",
                 ex.Message,
                 ex.StackTrace,
                 ex.GetType().ToString(),
                 errMessage);
            LogMessageToEventLog(exMessage);
        }

        private static void LogMessageToEventLog(string message)
        {
            try
            {
                if (!EventLog.SourceExists("LogHelper"))
                {
                    EventLog.CreateEventSource("LogHelper", "Application");
                }
                EventLog.WriteEntry("LogHelper", message);
            }
            catch (Exception innerEx)
            {
#if DEBUG
                // 如果连系统日志也无法写入，只能忽略该错误了。
                Console.WriteLine(String.Format("写入系统日志出错，原始日志信息：{2}\r\n异常信息：{0}\r\n堆栈信息：{1}", innerEx.Message, innerEx.StackTrace, message));
                //throw;
#endif
            }
        }

        private static void WriteIntoFile(string message, string path, string filename)
        {

            OpenedFile openFile;
            bool ret = _files.TryGetValue(path.ToLower() + filename, out openFile);
            if (!ret)
            {
                CreateDirectory(path);
                StreamWriter sw = new StreamWriter(path + filename, true);
                TextWriter writer = TextWriter.Synchronized(sw);
                openFile.Filename = path + filename;
                openFile.Writer = writer;
                _files.Add(path.ToLower() + filename, openFile);
            }
            openFile.Writer.WriteLine(message);
            openFile.Writer.Flush();
            DateTime dt = DateTime.Now;
            openFile.LastWriteTime = dt;
            //_sweepCount++;
            //if (_sweepCount > 10000)
            //{
            //    dt = SweepFiles(dt);
            //    _sweepCount = 0;
            //}
            if ((dt - _lastSweepTime).TotalMinutes > 1)
            {
                dt = SweepFiles(dt);
                _lastSweepTime = dt;
            }
        }

        private static DateTime SweepFiles(DateTime dt)
        {
            List<string> keys = new List<string>();
            foreach (var file in _files)
            {
                if ((dt - file.Value.LastWriteTime).TotalMinutes > 1)
                {
                    file.Value.Writer.Flush();
                    file.Value.Writer.Close();
                    file.Value.Writer.Dispose();
                    keys.Add(file.Key);
                }
            }
            foreach (string key in keys)
            {
                _files.Remove(key);
            }
            return dt;
        }

        /// <summary>
        /// 写入日志文件
        /// </summary>
        /// <param name="message"></param>
        /// <param name="filename"></param>
        private static void WriteFile(string message, string filename)
        {

            try
            {
                Monitor.Enter(sync);
                //CreateDirectory(filename);

                StreamWriter sw = new StreamWriter(filename, true);
                TextWriter writer = TextWriter.Synchronized(sw);
                writer.WriteLine(message);
                writer.Flush();
                writer.Close();
            }
#if DEBUG
            catch (Exception ex)
            {
                Console.WriteLine("写入文件日志出错，错误信息：" + ex.Message);
            }
#else
            catch
            {
            }
#endif
            finally
            {
                Monitor.Exit(sync);
            }
        }


        /// <summary>
        /// 创建目录
        /// </summary>
        /// <param name="directory"></param>
        private static void CreateDirectory(string directory)
        {
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }
        }       

        /// <summary>
        /// 获取异常的简写字符
        /// </summary>
        /// <param name="eLevel">异常等级</param>
        /// <returns></returns>
        private static string GetToken(ExceptionLevel eLevel)
        {
            switch (eLevel)
            {
                case ExceptionLevel.Warning:
                    return "W";
                case ExceptionLevel.Infomation:
                    return "I";
                case ExceptionLevel.Exception:
                    return "E";
                case ExceptionLevel.Error:
                    return "!";
                case ExceptionLevel.None:
                    return "";
                default:
                    return "?";
            }
        }
    }
}
